﻿// Partial Copyright (c) LogoUI Software Solutions LTD
// Autor: Vladislav Spivak
// This source file is the part of LogoFX Framework http://logofx.codeplex.com
// See accompanying licences and credits.

//inspired by Caliburn.Micro logging facilities http://caliburnmicro.codeplex.com

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using LogoFX.Core;

namespace System
{
    #region Interfaces
    /// <summary>
    /// A logger.
    /// </summary>
    public interface ILogger
    {
        /// <summary>
        /// Logs the message as info.
        /// </summary>
        /// <param name="format">A formatted message.</param>
        /// <param name="args">Parameters to be injected into the formatted message.</param>
        void Info(string format, params object[] args);

        /// <summary>
        /// Logs the message as a warning.
        /// </summary>
        /// <param name="format">A formatted message.</param>
        /// <param name="args">Parameters to be injected into the formatted message.</param>
        void Warn(string format, params object[] args);

        /// <summary>
        /// Logs the exception.
        /// </summary>
        /// <param name="exception">The exception.</param>
        void Error(Exception exception);

        /// <summary>
        /// Errors with the specified format.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="args">The args.</param>
        void Error(string format, params object[] args);

        /// <summary>
        /// Errors with the specified format.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="args">The args.</param>
        void Error(Exception exception, string format, params object[] args);

        /// <summary>
        /// Debugs with the specified format.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="args">The args.</param>
        void Debug(string format, params object[] args);

        /// <summary>
        /// Traces with the specified format.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="args">The args.</param>
        void Trace(string format, params object[] args);
    }

    /// <summary>
    /// Represents dispossable logger that useful in 'using' statement
    /// and outputs 'Enter' and 'Exit' markers
    /// </summary>
    public interface IDispossableLogger : IMethodAwareLogger,IDisposable
    {

    }

    /// <summary>
    /// Represents loggger, that prefixes each outputted string with method name
    /// </summary>
    public interface IMethodAwareLogger : ILogger
    {

    }

    #endregion

    #region extensions
    public static class LoggerExtensions
    {
        public static IMethodAwareLogger InMethod(this ILogger that, string method)
        {
            return Log.GetLogger(that, method);
        }
    }
    #endregion

    /// <summary>
    /// Static common logging access helper
    /// </summary>
    public static class Log
    {
        static readonly ILogger NullLogInstance = new NullLog();
        static readonly ThreadLocal<Stack<ILogger>> Loggers = new ThreadLocal<Stack<ILogger>>(() => new Stack<ILogger>(new []{NullLogInstance}));
        public static Func<Type, ILogger> GetLog = type => NullLogInstance;

        #region MethodAware
        private class MethodAwareLogger : IMethodAwareLogger
        {
            private readonly Type _type;
            private readonly string _methodName;
            private ILogger _externalLogger;

            public MethodAwareLogger(ILogger external,string methodName)
            {
                if (external == null)
                    throw new ArgumentNullException("external");
                if (string.IsNullOrWhiteSpace(methodName))
                    throw new ArgumentException("type should be not null and not empty string");

                _externalLogger = external;
                _methodName = methodName;

            }

            public MethodAwareLogger(Type type, string methodName)
            {
                if (type == null)
                    throw new ArgumentNullException("type");
                if (string.IsNullOrWhiteSpace(methodName))
                    throw new ArgumentException("type should be not null and not empty string");

                _type = type;
                _methodName = methodName;
            }

            private ILogger GetLog(Type t)
            {
                return _externalLogger ?? Log.GetLog(_type);
            }

            private ILogger InnerLogger
            {
                get { return GetLog(Type); }
            }

            protected Type Type
            {
                get { return _type; }
            }

            protected string MethodName
            {
                get { return _methodName; }
            }

            private string WithMethod(string s)
            {
                return string.Format("{0}:{1}", MethodName, s);
            }

            public void Info(string format, params object[] args)
            {
                InnerLogger.Info(WithMethod(format), args);
            }

            public void Warn(string format, params object[] args)
            {
                InnerLogger.Warn(WithMethod(format), args);
            }

            public void Error(Exception exception)
            {
                InnerLogger.Error(exception);
            }

            public void Error(string format, params object[] args)
            {
                InnerLogger.Error(WithMethod(format), args);
            }

            public void Error(Exception exception, string format, params object[] args)
            {
                InnerLogger.Error(exception, WithMethod(format), args);
            }

            public void Debug(string format, params object[] args)
            {
                InnerLogger.Debug(WithMethod(format), args);
            }

            public void Trace(string format, params object[] args)
            {
                InnerLogger.Trace(WithMethod(format), args);
            }
        }
        #endregion
        
        #region DispossableLogger
        private class DispossableLogger : MethodAwareLogger, IDispossableLogger
        {

            public DispossableLogger(Type type, string methodName)
                : base(type 
#if !WinRT
                ?? new StackTrace().GetFrame(2).GetMethod().DeclaringType
#endif
                , 
#if !WinRT
                string.IsNullOrWhiteSpace(methodName) ? new StackTrace().GetFrame(2).GetMethod().Name : methodName
#else
                methodName
#endif
                )
            {
                Loggers.Value.Push(this);
                Trace("Enter");
            }
            public DispossableLogger(ILogger logger, string methodName)
                : base(logger 
#if !WinRT
                ?? Log.GetLog(new StackTrace().GetFrame(2).GetMethod().DeclaringType)
#endif

                ,
#if !WinRT
                string.IsNullOrWhiteSpace(methodName) ? new StackTrace().GetFrame(2).GetMethod().Name : methodName
#else
                methodName
#endif
                )
            {
                Loggers.Value.Push(this);
                Trace("Enter");
            }
            public void Dispose()
            {
                Trace("Exit");
                Loggers.Value.Pop();
            }
        }
        #endregion

        #region nullLog
        private class NullLog : ILogger
        {
            public void Info(string format, params object[] args) { }
            public void Warn(string format, params object[] args) { }
            public void Error(Exception exception) { }
            public void Error(string format, params object[] args)
            {
            }

            public void Error(Exception exception, string format, params object[] args)
            {
            }

            public void Debug(string format, params object[] args)
            {

            }

            public void Trace(string format, params object[] args)
            {
            }
        }
        #endregion

        #region static accessors

        /// <summary>
        /// Gets the logger for method based on existing <see cref="ILogger"/>.
        /// </summary>
        /// <param name="logger">The external logger.</param>
        /// <param name="method">The method.</param>
        /// <returns></returns>
        public static IMethodAwareLogger GetLogger(ILogger logger, string method)
        {
            return new MethodAwareLogger(logger, method);
        }

        /// <summary>
        /// Gets the logger based on type and method name.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        /// <returns></returns>
        public static IMethodAwareLogger GetLogger(Type type, string method)
        {
            return new MethodAwareLogger(type, method);
        }

        /// <summary>
        /// Pushes the specified logger on thread local stack
        /// </summary>
        /// <remarks>used by postsharp framer</remarks>
        /// <param name="logger">The logger.</param>
        public static void Push(ILogger logger)
        {
            Loggers.Value.Push(logger);
        }

        /// <summary>
        /// Pops logger from thread local stack
        /// </summary>
        /// <remarks>used by postsharp framer</remarks>
        /// <returns></returns>
        public static ILogger Pop()
        {
            return Loggers.Value.Pop();
        }

        /// <summary>
        /// Returns dispossable logger based on type and method name
        /// </summary>
        /// <remarks>use this inside 'using' statement</remarks>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        /// <returns>newly created <see cref="IDispossableLogger"/></returns>
        public static IDispossableLogger Frame(Type type = null, string method = null)
        {
            return new DispossableLogger(type, method);
        }

        /// <summary>
        /// Returns dispossable logger based on other logger and method name
        /// </summary>
        /// <param name="logger">The logger.</param>
        /// <param name="method">The method.</param>
        /// <returns>newly created <see cref="IDispossableLogger"/></returns>
        public static IDispossableLogger Frame(ILogger logger = null, string method = null)
        {
            return new DispossableLogger(logger, method);
        }

        /// <summary>
        /// Infoes in specified format.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="args">The args.</param>
        public static void Info(string format, params object[] args)
        {
            EnsureLogger();
            Loggers.Value.Peek().Info(format, args);
        }

        /// <summary>
        /// Traces in specified format.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="args">The args.</param>
        public static void Trace(string format, params object[] args)
        {
            EnsureLogger();
            Loggers.Value.Peek().Trace(format, args);
        }

        /// <summary>
        /// Debugs in specified format.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="args">The args.</param>
        public static void Debug(string format, params object[] args)
        {
            EnsureLogger();
            Loggers.Value.Peek().Debug(format, args);
        }

        /// <summary>
        /// Warns in specified format.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="args">The args.</param>
        public static void Warn(string format, params object[] args)
        {
            EnsureLogger();
            Loggers.Value.Peek().Warn(format, args);
        }

        /// <summary>
        /// Errors in specified format.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="args">The args.</param>
        public static void Error(string format, params object[] args)
        {
            EnsureLogger();
            Loggers.Value.Peek().Error(format, args);
        }

        /// <summary>
        /// Errors the specified exception.
        /// </summary>
        /// <param name="exception">The exception.</param>
        public static void Error(Exception exception)
        {
            EnsureLogger();
            Loggers.Value.Peek().Error(exception);
        }

        #endregion

        #region private
        private static void EnsureLogger()
        {
            //if (_loggers.Value.Count < 1)
            //    throw new InvalidOperationException("No Loggers On Stack");
        }
        #endregion
    }
}